package chathub

import (
	"context"
	"github.com/gin-gonic/gin"
	"github.com/panjf2000/ants/v2"
	"google.golang.org/protobuf/proto"
	"mychat/api"
	"mychat/internal/common/cmctx"
	"mychat/internal/common/logger"
	"mychat/internal/common/shard"
	"mychat/internal/conf"
	"mychat/internal/data"
	"mychat/internal/service/chat"
	"mychat/internal/service/user"
)

type ChatService interface {
	GetHub() *chatHub
	ServeWs() gin.HandlerFunc
}

type chatHub struct {
	clients    shard.Shards
	register   chan *chatClient
	unregister chan *chatClient
	hc         *conf.ChatHub
	lg         logger.Logger
	dt         *data.Data
	userSvc    user.UserService
	chatSvc    chat.ChatService
	pool       *ants.Pool
}

func NewChatHubService(hc *conf.ChatHub, lg logger.Logger, dt *data.Data,
	userSvc user.UserService, chatSvc chat.ChatService) ChatService {
	hub := &chatHub{
		register:   make(chan *chatClient, hc.ClientChLen),
		unregister: make(chan *chatClient, hc.ClientChLen),
		clients:    shard.NewSmapShard(hc.ShardLen),
		hc:         hc,
		lg:         lg.WithCustomTag(logger.LoggerTagWebsocket),
		dt:         dt,
		userSvc:    userSvc,
		chatSvc:    chatSvc,
	}

	go hub.run()

	return hub
}

func (h *chatHub) run() {
	h.pool, _ = ants.NewPool(32)
	defer h.pool.Release()

	for {
		select {
		case client := <-h.register:
			userId := cmctx.GetCtxUserId(client.ctx)
			h.clients.Set(userId, client)
		case client := <-h.unregister:
			userId := cmctx.GetCtxUserId(client.ctx)
			h.clients.Delete(userId)
			client.closeSend()
		}
	}
}

func (h *chatHub) BroadCast(opCode api.OpCode, send *api.Send) {
	err := ants.Submit(func() {
		userId := send.ToUserId
		val, ok := h.clients.Get(uint(userId))
		if !ok {
			// TODO: 离线，缓存待发送消息
			return
		}

		client := val.(*chatClient)

		resp := &api.Response{
			Code:    0,
			Payload: nil,
			OpCode:  opCode,
		}
		resp.Payload, _ = proto.Marshal(send)

		respb, _ := proto.Marshal(resp)
		client.send <- respb
	})
	if err != nil {
		h.lg.Error(context.Background(), "hub pool submit task failed||err=%v", err)
	}
}

func (h *chatHub) GetHub() *chatHub {
	return h
}

// chathub
// @Summary websocket连接
// @Description 建立websocket连接
// @Tags hub
// @Success 200
// @Failure 400
// @Failure 404
// @Router /v1/ws [get]
func (h *chatHub) ServeWs() gin.HandlerFunc {
	return func(gtx *gin.Context) {
		userId := cmctx.GetCtxUserId(gtx.Request.Context())
		if userId == 0 {
			h.lg.ErrorCtx(gtx, "GetCtxUserId failed||userId=0")
			return
		}

		conn, err := upgrader.Upgrade(gtx.Writer, gtx.Request, nil)
		if err != nil {
			h.lg.ErrorCtx(gtx, "upgrader.Upgrade failed||err=%v", err)
			return
		}

		client := &chatClient{
			hub:  h,
			conn: conn,
			send: make(chan []byte, 256),
			ctx:  cmctx.SetCtxUserId(context.Background(), userId),
		}
		client.hub.register <- client

		go client.writePump()
		go client.readPump()
	}
}
